# frozen_string_literal: true

require_relative '../unit_helper'

module Watir
  module Locators
    class Element
      describe Matcher do
        include LocatorSpecHelper

        let(:query_scope) { @query_scope || browser }
        let(:values_to_match) { @values_to_match || {} }
        let(:matcher) { described_class.new(query_scope, values_to_match) }

        describe '#match' do
          context 'when a label element' do
            it 'returns elements with for / id pairs' do
              input_wds = [wd_element(tag_name: 'input', attributes: {id: 'foob_id'}),
                           wd_element(tag_name: 'input', attributes: {id: 'bfoo_id'}),
                           wd_element(tag_name: 'input', attributes: {id: 'foo_id'})]

              label_wds = [wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label')]

              allow(browser).to receive(:execute_script).and_return('foob', 'Foo', 'foo')

              labels = [element(watir_element: Watir::Label, wd: label_wds[0], for: 'foob_id'),
                        element(watir_element: Watir::Label, wd: label_wds[1], for: 'bfoo_id'),
                        element(watir_element: Watir::Label, wd: label_wds[2], for: 'foo_id')]

              allow(query_scope).to receive(:labels).and_return(labels)
              allow_any_instance_of(Watir::Input).to receive(:wd).and_return(input_wds[2], input_wds[2])

              values_to_match = {label_element: 'foo'}
              expect(matcher.match(input_wds, values_to_match, :all)).to eq [input_wds[2]]
              # Only the Watir::Label matching the text provided will have wd called
              expect(labels[0]).not_to have_received(:for)
              expect(labels[1]).not_to have_received(:for)
            end

            it 'returns elements without for / id pairs' do
              input_wds = [wd_element(tag_name: 'input'),
                           wd_element(tag_name: 'input'),
                           wd_element(tag_name: 'input')]

              inputs = [element(watir_element: Watir::Input, wd: input_wds[0]),
                        element(watir_element: Watir::Input, wd: input_wds[1]),
                        element(watir_element: Watir::Input, wd: input_wds[2])]

              label_wds = [wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label')]

              allow(browser).to receive(:execute_script).and_return('foob', 'Foo', 'foo')

              labels = [element(watir_element: Watir::Label, wd: label_wds[0], for: '', input: inputs[0]),
                        element(watir_element: Watir::Label, wd: label_wds[1], for: '', input: inputs[1]),
                        element(watir_element: Watir::Label, wd: label_wds[2], for: '', input: inputs[2])]

              allow(query_scope).to receive(:labels).and_return(labels)

              values_to_match = {label_element: 'foo'}
              expect(matcher.match(input_wds, values_to_match, :all)).to eq [input_wds[2]]

              # Only the Watir::Label matching the text provided will have wd called
              expect(labels[0]).not_to have_received(:for)
              expect(labels[1]).not_to have_received(:for)
            end

            it 'returns elements with multiple matching label text but first missing corresponding element' do
              input_wds = [wd_element(tag_name: 'input'),
                           wd_element(tag_name: 'input')]

              inputs = [element(watir_element: Watir::Input, wd: wd_element(tag_name: 'input')),
                        element(watir_element: Watir::Input, wd: input_wds[1])]

              label_wds = [wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label')]

              allow(browser).to receive(:execute_script).and_return('foo', 'foo')

              labels = [element(watir_element: Watir::Label, wd: label_wds[0], for: '', input: inputs[0]),
                        element(watir_element: Watir::Label, wd: label_wds[1], for: '', input: inputs[1])]

              allow(query_scope).to receive(:labels).and_return(labels)

              values_to_match = {label_element: 'foo'}

              expect(matcher.match(input_wds, values_to_match, :all)).to eq [input_wds[1]]
            end

            it 'returns empty Array if no label element matches' do
              input_wds = [wd_element(tag_name: 'input', attributes: {id: 'foo'}),
                           wd_element(tag_name: 'input', attributes: {id: 'bfoo'})]

              label_wds = [wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label')]

              allow(browser).to receive(:execute_script).and_return('Not this', 'or this')

              labels = [element(watir_element: Watir::Label, wd: label_wds[0], for: 'foo'),
                        element(watir_element: Watir::Label, wd: label_wds[1], for: 'bfoo')]

              allow(query_scope).to receive(:labels).and_return(labels)

              values_to_match = {label_element: 'foo'}

              expect(matcher.match(input_wds, values_to_match, :all)).to eq []
            end

            it 'returns empty Array if matching label elements do not have an corresponding input element' do
              input_wds = [wd_element(tag_name: 'input'),
                           wd_element(tag_name: 'input')]

              inputs = [element(watir_element: Watir::Input, wd: wd_element(tag_name: 'input')),
                        element(watir_element: Watir::Input, wd: wd_element(tag_name: 'input'))]

              label_wds = [wd_element(tag_name: 'label'),
                           wd_element(tag_name: 'label')]

              allow(browser).to receive(:execute_script).and_return('foob', 'foo')

              labels = [element(watir_element: Watir::Label, wd: label_wds[0], for: '', input: inputs[0]),
                        element(watir_element: Watir::Label, wd: label_wds[1], for: '', input: inputs[1])]

              allow(query_scope).to receive(:labels).and_return(labels)

              values_to_match = {label_element: 'foo'}

              expect(matcher.match(input_wds, values_to_match, :all)).to eq []
            end
          end

          context 'when locating one element' do
            before { @filter = :first }

            it 'by tag name' do
              elements = [wd_element(tag_name: 'div'),
                          wd_element(tag_name: 'span')]
              @values_to_match = {tag_name: 'span'}

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[1]
            end

            it 'by attribute' do
              elements = [wd_element(attributes: {id: 'foo'}),
                          wd_element(attributes: {id: 'bar'}),
                          wd_element(attributes: {id: 'foobar'})]
              @values_to_match = {id: 'foobar'}

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[2]
            end

            it 'by class array' do
              elements = [wd_element(attributes: {class: 'foob bar'}),
                          wd_element(attributes: {class: 'bar'}),
                          wd_element(attributes: {class: 'bar foo'})]
              @values_to_match = {class: %w[foo bar]}

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[2]
            end

            it 'by positive index' do
              elements = [wd_element(tag_name: 'div'),
                          wd_element(tag_name: 'span'),
                          wd_element(tag_name: 'div')]

              @values_to_match = {tag_name: 'div', index: 1}

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[2]
            end

            it 'by negative index' do
              elements = [wd_element(tag_name: 'div'),
                          wd_element(tag_name: 'span'),
                          wd_element(tag_name: 'div')]

              @values_to_match = {tag_name: 'div', index: -1}

              expect(matcher.match(elements.dup, values_to_match, @filter)).to eq elements[2]
            end

            it 'by visibility true' do
              elements = [wd_element(tag_name: 'div', displayed?: false),
                          wd_element(tag_name: 'span', displayed?: true)]
              @values_to_match = {visible: true}

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[1]
            end

            it 'by visibility false' do
              elements = [wd_element(tag_name: 'div', displayed?: true),
                          wd_element(tag_name: 'span', displayed?: false)]
              @values_to_match = {visible: false}

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[1]
            end

            it 'by text' do
              elements = [wd_element, wd_element]
              @values_to_match = {text: 'Foob'}

              allow(browser).to receive(:execute_script).and_return('foo', 'Foob')

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[1]
            end

            it 'by visible text' do
              elements = [wd_element(text: 'foo'),
                          wd_element(text: 'Foob')]
              @values_to_match = {visible_text: /Foo|Bar/}

              allow(elements[0]).to receive(:text).and_return 'foo'
              allow(elements[1]).to receive(:text).and_return 'Foob'

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[1]
            end

            it 'by href' do
              elements = [wd_element(tag_name: 'div', attributes: {href: 'froo.com'}),
                          wd_element(tag_name: 'span', attributes: {href: 'bar.com'})]
              @values_to_match = {href: /foo|bar/}

              expect(matcher.match(elements, values_to_match, @filter)).to eq elements[1]
            end

            it "returns nil if found element doesn't match the selector tag_name" do
              elements = [wd_element(tag_name: 'div')]

              @values_to_match = {tag_name: 'span'}

              expect(matcher.match(elements, values_to_match, @filter)).to be_nil
            end
          end

          context 'when locating collection' do
            before { @filter = :all }

            it 'by tag name' do
              elements = [wd_element(tag_name: 'div'),
                          wd_element(tag_name: 'span'),
                          wd_element(tag_name: 'div')]
              @values_to_match = {tag_name: 'div'}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end

            it 'by attribute' do
              elements = [wd_element(attributes: {foo: 'foo'}),
                          wd_element(attributes: {foo: 'bar'}),
                          wd_element(attributes: {foo: 'foo'})]
              @values_to_match = {foo: 'foo'}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end

            it 'by single class' do
              elements = [wd_element(attributes: {class: 'foo bar cool'}),
                          wd_element(attributes: {class: 'foob bar'}),
                          wd_element(attributes: {class: 'bar foo foobar'})]
              @values_to_match = {class: 'foo'}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end

            it 'by class array' do
              elements = [wd_element(attributes: {class: 'foo bar cool'}),
                          wd_element(attributes: {class: 'foob bar'}),
                          wd_element(attributes: {class: 'bar foo foobar'})]
              @values_to_match = {class: %w[foo bar]}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end

            it 'by visibility true' do
              elements = [wd_element(tag_name: 'div'),
                          wd_element(tag_name: 'span'),
                          wd_element(tag_name: 'div')]
              @values_to_match = {visible: true}

              allow(elements[0]).to receive(:displayed?).and_return false
              allow(elements[1]).to receive(:displayed?).and_return true
              allow(elements[2]).to receive(:displayed?).and_return true

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[1], elements[2]]
            end

            it 'by visibility false' do
              elements = [wd_element(displayed?: false),
                          wd_element(displayed?: true),
                          wd_element(displayed?: false)]
              @values_to_match = {visible: false}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end

            it 'by text' do
              elements = [wd_element, wd_element, wd_element]
              @values_to_match = {text: /Foo|Bar/}

              allow(browser).to receive(:execute_script).and_return('foo', 'Foob', 'bBarb')

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[1], elements[2]]
            end

            it 'by visible text' do
              elements = [wd_element(text: 'foo'),
                          wd_element(text: 'Foob'),
                          wd_element(text: 'bBarb')]
              @values_to_match = {visible_text: /Foo|Bar/}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[1], elements[2]]
            end

            it 'by href' do
              elements = [wd_element(attributes: {href: 'froo.com'}),
                          wd_element(attributes: {href: 'bar.com'}),
                          wd_element(attributes: {href: 'foobar.com'})]
              @values_to_match = {href: /foo|bar/}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[1], elements[2]]
            end

            it 'returns empty Array if found no element matches the selector tag_name' do
              elements = [wd_element(tag_name: 'div')]

              @values_to_match = {tag_name: 'span'}

              expect(matcher.match(elements, values_to_match, @filter)).to eq []
            end
          end

          context 'when matching Regular Expressions' do
            it 'with white space' do
              allow(browser).to receive(:execute_script).and_return("\n match this \n", 'no', 'match  this')

              elements = [wd_element,
                          wd_element,
                          wd_element]
              @values_to_match = {text: /^match this$/}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end

            it 'with tag_name' do
              elements = [wd_element(tag_name: 'div'),
                          wd_element(tag_name: 'span'),
                          wd_element(tag_name: 'div')]
              @values_to_match = {tag_name: /d|q/}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end

            it 'with single class' do
              elements = [wd_element(attributes: {class: 'foo bar cool'}),
                          wd_element(attributes: {class: 'foob bar'}),
                          wd_element(attributes: {class: 'bar foo foobar'})]
              @values_to_match = {class: /foob|q/}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[1], elements[2]]
            end

            it 'with multiple classes' do
              elements = [wd_element(attributes: {class: 'foo bar cool'}),
                          wd_element(attributes: {class: 'foob bar'}),
                          wd_element(attributes: {class: 'bar foo foobar'})]
              @values_to_match = {class: [/foob/, /bar/]}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[1], elements[2]]
            end

            it 'with attributes' do
              elements = [wd_element(attributes: {foo: 'foo'}),
                          wd_element(attributes: {foo: 'bar'}),
                          wd_element(attributes: {foo: 'foo'})]
              @values_to_match = {foo: /fo/}

              expect(matcher.match(elements, values_to_match, @filter)).to eq [elements[0], elements[2]]
            end
          end

          context 'when matching text Regexp' do
            let(:elements) { [wd_element, wd_element] }

            before { allow(browser).to receive(:execute_script).and_return('all visible', 'some visible') }

            it 'not thrown when still matched by text content' do
              values_to_match = {text: /some visible/}

              expect(matcher.match(elements, values_to_match, :first)).to eq elements[1]
            end

            it 'not thrown with complex regexp matched by text content' do
              values_to_match = {text: /some (in|)visible/}

              expect(matcher.match(elements, values_to_match, :first)).to eq elements[1]
            end

            it 'thrown when no longer matched by text content' do
              values_to_match = {text: /some visible$/}

              expect(matcher.match(elements, values_to_match, :first)).to eq elements[1]
            end

            it 'not thrown when element does not exist' do
              values_to_match = {text: /definitely not there/}

              expect(matcher.match(elements, values_to_match, :first)).to be_nil
            end

            it 'keeps element from being located' do
              values_to_match = {text: /some visible some hidden/}

              expect(matcher.match(elements, values_to_match, :first)).to be_nil
            end
          end
        end
      end
    end
  end
end
